// ES5新增的一些数组方法
    // 数组方法的用法
        var arr = [1, 2, 3, 4, 5]   

    // forEach() 遍历数组 第一个参数value是数组的值， 第二个参数index是索引号， 第三个是数组本身
        arr.forEach((value, index, array) => {
            console.log(`数组的值${value}      数组的索引号${index}    数组本身${array}`);
        })

    // map() 返回一个新数组 符合条件的值变成true 不符合条件的值变成false
        var result = arr.map((value, index, array) => {
            return value >= 4;
        })
        console.log(result);   //例如：[false, false, false, true, true]

    // filter() 用来创建一个新数组，新数组中的元素是通过检查指定数组中符合条件的所有元素，主要用于筛选数组   注意：它会返回一个新数组
        var result = arr.filter((value, index, array) => {
            return vlaue >= 4  //筛选条件大于等于4的值，它会返回符合条件的值，并且放到一个新数组中
        })
        console.log(result);

    // some() 方法用于检测数组中满足条件的元素，找到则返回true，找不到则返回false  注意：找到第一个符合条件的则停止循环(返回true停止循环)
        var result = arr.some((value, index, array) => {
            return value >= 4;
        })
        console.log(result) //返回布尔值true

    // every() 方法用于检测数组中满足条件的元素，符合则继续循环，不符合条件的则返回false即刻停止循环
        var result = arr.every((vlaue, index, array) => {
            return value <= 4 //判断条件：小于等于4的，遇到不符合条件的返回false，停止循环
        })

    // reduce() 方法用于计算数组中符合条件的个数
        let result = arr.reduce((pre, current) => {
            return pre + (current < 5);
        }, 0)
    
    // sort() 方法用于排序
        const arr = [1, 9, 8, 10, 20, 11, 7, 5]
        let result = arr.sort((a, b) => a - b); // 升序
        let result = arr.sort((a, b) => b - a); // 降序





// 为什么要学习ES6
    // 1.ES6的版本变动内容最多，具有里程碑意义
    // 2.ES6加入许多新的语法特性，编程实现更简单、高效



// ES6的兼容性
    //http://kangax.github.io/compat-table/es6/ 可以查看兼容性


// let关键字
    // 注意
    // 1.变量不能重复声明
    // 2.有块级作用域：全局作用域、函数作用域、eval作用域(eval在es5的严格模式中才有)
    // 变量声明
    let a;
    let b,c,d;
    let e = 100;

// const关键字(常量)
    // 何为常量：值不能修改的变量就称为常量
    // 注意：
    // 1.常量一定要赋初始值
    // 2.常量一般使用大写(潜规则，不是语法要求)
    // 3.常量的值不能修改
    // 4.有块级作用域
    // 5.对于数组和对象的修改不算做对常量的修改，不会报错，一般声明数组或对象使用常量来声明
    const SCHOOL = '惠州城市职业技术学院';
    


// 解构赋值
    // ES6允许按照一定模式从数组或对象中提取值，对变量进行赋值，这被称为解构赋值。
    // 1.数组解构赋值
        const ARR = [1, 2, 3];
        // 相当于把数组的第0为赋值给a，第一位赋值给b ......
        let [a, b, c] = ARR;
        console.log(a);
        console.log(b);
        console.log(c);
    // 2.对象解构赋值
        const OBJ = {
            name : 'zhangsan',
            age : 18,
            sex : 'famel',
            sing : function() {
                console.log('sing');
            }
        }
        // 相当于把OBJ.name赋值给变量name ......
        let {name, age , sex, sing} = OBJ;
        console.log(name)
        console.log(age)
        console.log(sex)
        sing();




// 模板字符串
    // 模板字符串的特性
        // 1.内容中可以直接出现换行符
        // 2.变量和字符串拼接更加方便，在字符串中使用${变量名}就可以实现拼接
        let a = '张三';
        let str = `我是${a}`;
        console.log(str);





// ES6简化对象的写法
    // 允许在大括号里面，直接写入变量和函数，作为对象的属性和方法。
    // 这样的书写更加简洁。
        let name = 'zhangsan';
        let change = function() {
            console.log('改变')
        }
        const OBJ = {
            name,
            change,
            //方法的简化
            remote() {
                console.log('remote');
            }
        }
        /*
        OBJ的写法等效于

        const obj = {
            name : name,
            change : change,
            remote : function() {
                console.log('remote')
            }
        }
        */
        console.log(OBJ.name);
        OBJ.sing()
        OBJ.remote();







// 箭头函数
    // 箭头函数的特性
        // 1.this始终指向函数声明时所在的作用域下，它自身没有this(无法通过call或者applay来改变this)
        // 2.不能作为构造函数实例化对象
        // 3.不能使用arguments变量
        // 4.箭头函数的简写，分为两种情况
            // 1).省略小括号，当形参有且只有一个的时候可以省略小括号
                    let fn = name => {
                        console.log(name);
                    }
                    fn('zhangsan');
            // 2).省略大括号，当代码体只有一条语句的时候可以省略大括号，此时return必须省略，而且语句的执行结构就是函数的返回值
                    let fn = (a, b) => a + b;
                    console.log(fn(1, 2));
            // 3.省略小括号和大括号,当形参只有一个并且代码体只有一条语句的时候。
                    let fn = a => a;
                    console.log(a('zhangsan'));

    
    // 声明一个箭头函数
    let fn = () => {
        console.log("我是箭头函数")
    }

    // 箭头函数总结：
        // 箭头函数适合与 this 无关的回调，定时器，数组的方法回调
        // 箭头函数不适合与 this 有关的回调，事件回调，对象的方法











// 函数参数的默认值设置
    // ES6 允许给函数参数赋初始值(给形参设初始值)
    // 特性：
        // 1.如果设置了初始值，你传递了实参的值就使用实参，如果没有传递就使用初始值
        // 2.具有默认值的参数一般靠后(潜规则)
        // 3.可以和解构赋值相结合

    function fn(a, b, c=10) {
        return a + b + c;
    }
    console.log(fn(1, 2));


    // 和解构赋值相结合使用方法(在这里面也可以赋初始值)：
        function fn({host='127.0.0.1', username, password, port=80}) {
            console.log(host);
            console.log(username);
            console.log(password);
            console.log(port);
        }
        fn({
            host : 'localhost',
            username : 'root',
            password : 'root',
            port : '3389'
        })






// reset参数
    // ES6引入了rest参数，用来获取函数的实参，代替arguments
    // 注意： rest参数必须要放到参数的最后面,否则会报错
    function fn(...args) {
        console.log(args);   //获取到的参数是一个数组
    }
    fn('zhangsan', 'lisi', 'wangwu');







// 扩展运算符 ...
    // 1.扩展运算符可以将数组转换为逗号分割的参数形式
        const TF = ['zhangsan', 'lisi', 'wangwu'];
        function fn() {
            console.log(arguments);
        }
        fn(...TF)  //相当于把数组的每一位拆分出来当做实参传递到函数里面 这个写法等于fn('zhangsan', 'lisi', 'wangwu');


    // 2.扩展运算符用于数组合并
        const TF = ['zs', 'sl', 'ww'];
        const TF1 = ['wjk', 'yyqx' ,'wy'];
        const TF2 = [...TF ,...TF1]
        console.log(TF2);


    // 3.数组克隆
        const TF = ['zs', 'sl', 'ww'];
        const TF1 = [...TF];
        console.log(TF == TF1)

    // 4.将伪数组转为真正的数组
        let wrapper = document.querySelector('.wrapper');
        let div = wrapper.querySelectorAll('div');
        const ARR = [...div];
        console.log(ARR);







// symbol
    // ES6引入了一种新的原始数据类型symbol，表示独一无二的值。它是JavaScript语言的
    // 第七种数据类型，是一种类似于字符串的数据类型。

    // symbol特点：
        // 1.symbol的值是惟一的，用来解决命名冲突的问题
        // 2.symbol值不能与其他数据进行运算
        // symbol定义的对象属性不能使用for...in循环遍历，但是可以使用Reflect.ownKeys来获取对象的所有键名。



    // 创建symbol
        let s1 = Symbol();
        console.log(s1, typeof(s1));

    // 添加标识(可以理解成注释)
        let s1 = Symbol('我是symbol类型的数据');
        console.log(s1, typeof(s1));
    
    // 使用symbol.for创建symbol
    // 作用：用于将描述相同的Symbol变量指向同一个Symbol值
        let s1 = Symbol.for('symbol type')
    


    // symbol的使用场景就是给对象添加属性和方法，表示独一无二的
        // 例如：我想给obj这个对象添加up和down方法但是我不知道这个对象里面有没有这两个方法，我不想覆盖它
        const obj = {
            up() {
                console.log('up');
            },
            down() {
                console.log('down');
            }
        }
        const obj1 = {
            up : Symbol,
            down : Symbol() 
        }
        obj[obj1.up] = function() {
            console.log('上升')
        }
        obj[obj1.down] = function() {
            console.log('下降')
        }
        obj.up();
        obj.down();
        obj[obj1.up]();
        obj[obj1.down]();

        // 给对象添加symbol方法的另外一种方式
        const obj = {
            name : 'zhangsan',
            [Symbol('say')] : function() {
                console.log('say')
            },
            [Symbol('say')] : function() {
                console.log('say1');
            }
        }
        // Reflect.ownKeys / Object.getOwnPropertySymbols() 来获取对象的所有键名
        // 这个方法用来获取对象里面的symbol属性，返回一个数组
        const sym = Object.getOwnPropertySymbols(obj);
        obj[sym[0]]();
        obj[sym[1]]();




// 记忆JavaScript数据类型的方法
    // USONB  you are so niubility(你是如此的牛逼)
    // U undefined
    // S string symbol
    // o object
    // n number null
    // b boolean



// 内置的Symbol值:
    // 除了定义自己使用的 Symbol 值以外，ES6 还提供了 11 个内置的 Symbol 值，指向语言内部使用的方法。
    // 可以称这些方法为魔术方法，因为它们会在特定的场景下自动执行。
    
    // 内置Symbol的值	调用时机
        // Symbol.hasInstance	当其他对象使用 instanceof 运算符，判断是否为该对象的实例时，会调用这个方法
        // Symbol.isConcatSpreadable	对象的 Symbol.isConcatSpreadable 属性等于的是一个布尔值，表示该对象用于 Array.prototype.concat()时，是否可以展开。
        // Symbol.species	创建衍生对象时，会使用该属性
        // Symbol.match	当执行 str.match(myObject) 时，如果该属性存在，会调用它，返回该方法的返回值。
        // Symbol.replace	当该对象被 str.replace(myObject)方法调用时，会返回该方法的返回值。
        // Symbol.search	当该对象被 str. search (myObject)方法调用时，会返回该方法的返回值。
        // Symbol.split	当该对象被 str. split (myObject)方法调用时，会返回该方法的返回值。
        // Symbol.iterator	对象进行 for…of 循环时，会调用 Symbol.iterator 方法，返回该对象的默认遍历器
        // Symbol.toPrimitive	该对象被转为原始类型的值时，会调用这个方法，返回该对象对应的原始类型值。
        // Symbol. toStringTag	在该对象上面调用 toString 方法时，返回该方法的返回值
        // Symbol. unscopables	该对象指定了使用 with 关键字时，哪些属性会被 with环境排除。








// 迭代器
    // 迭代器(iterator)是一种接口，为各种不同的数据结构提供统一的访问机制。
    // 任何数据结构只要部署(iterator)接口，就可以完成遍历操作。
        // 1)ES6创建了一种新的遍历命令for...of循环，iterator接口主要提供for....of消费
        // 2)原生具备iterator接口的数据(可以for of循环)
            // a) Array
            // b) Arguments
            // c) Set
            // d) Map
            // e) String
            // f) TypedArray
            // g) NodeList

        // 3.工作原理
            // a) 创建一个指针对象，指向当前数据结构的起始位置
            // b) 第一次调用对象的next方法，指针自动指向数据结构的第一个成员
            // c) 接下来不断调用next方法，指针一直往后面移，直到指向最后一个成员
            // 注意：需要自定义遍历数据的时候，要先想到迭代器

            // 迭代器案例
            // 自定义遍历数据:我需要办理OBJ这个对象里面的age属性里面的数组
            const OBJ = {
                name : 'zhangsan',
                age : [18, 20, 22, 25],
                [Symbol.iterator]() {
                    let index = 0;
                    return {
                        next : () => {
                            if(index < this.age.length) {
                                const result = {value : this.age[index], done : false}
                                index ++;
                                return result;
                            }else {
                                return {
                                    value : undefined,
                                    done : true
                                }
                            }
                        }
                    }
                }
            }
            // 遍历这个对象
            for(let i of OBJ) {
                console.log(i);
            }
            






// 生成器
    // 生成器函数是ES6提供的一种异步编程解决方案，语法行为于传统函数完全不同
    function * gen() {
        yield '1'
        yield '2'
    }

    // yield 语句是函数代码的分隔符,把函数代码切分成几块
    // 例如：
    function * gen() {
        console.log('a')
        yield 111;
        console.log('b');
        yield 222;
        console.log('c');
    }
    let iterator = gen();
    console.log(iterator.next()); 
    console.log(iterator.next());
    console.log(iterator.next());
    console.log(iterator.next());



    // next方法的传参问题
    function * gen(arg) {
        console.log(arg);
        let one = yield 111;
        console.log(one);
        let two = yield 222;
        console.log(two);
        yield 333;
        let three = console.log()
    }
    let iterator = gen('AAA');
    console.log(iterator.next('aaa')) //第二个next传的参数将作为第一个yield的返回值
    console.log(iterator.next('bbb')) //第三个next传的参数将作为第二个yield的返回值





    // 生成器函数实例(可以解决回调地狱的问题)
        // 1s后控制台输出 111
        // 2s后输出  222
        // 3s后输出 333

        // 原始解决方法
        setTimeout(() => {
            console.log(111);
            setTimeout(() => {
                console.log(222);
                setTimeout(() => {
                    console.log(333);
                }, 3000)
            }, 2000)
        }, 1000)
        

        // 异步编程解决方法
        function one() {
            setTimeout(() => {
                console.log(111);
                iterator.next();
            }, 1000)
        }
        function two() {
            setTimeout(() => {
                console.log(222);
                iterator.next();
            }, 2000)
        }
        function three() {
            setTimeout(() => {
                console.log(333);
                iterator.next();
            }, 3000)
        }

        function * gen() {
            yield one();
            yield two();
            yield three();
        }
        
        let iterator = gen();
        iterator.next();






    


    // 模拟获取 用户数据 订单数据 商品数据
    function getUser() {
        setTimeout(() => {
            let data = '用户数据';
            iterator.next(data);
        }, 1000)
    }
    function getOrders() {
        setTimeout(() => {
            let data = '订单数据';
            iterator.next(data);
        }, 1000)
    }
    function getGoods() {
        setTimeout(() => {
            let data = '商品数据';
            iterator.next(data);
        }, 1000)
    }
    function * gen() {
        let user = yield getUser();
        console.log(user);
        let orders = yield getOrders();
        console.log(orders);
        let goods = yield getGoods();
        console.log(goods);
    }
    let iterator = gen();
    iterator.next();









// Promise
    // Promise是ES6引入的异步编程的新的解决方案。语法是Promise是一个构造函数
    // 用来封装操作并可以获取其成功或失败的结果。
        // 1) Promise 构造函数 : permise(excutor) {}
        // 2) Promise.prototype.then 方法
        // 3) Promise.prototype.catch 方法


        // 1.实例化Promise
            // promise对象有三个状态：初始化    成功      失败
            // new Promise()方法它接收一个函数作为参数，函数里面有两个参数，分别是resolve, reject
            const promise = new Promise(function(resolve, reject) {
                setTimeout(() => {
                    let dataUser = '数据库中的用户数据';
                    resolve(dataUser);  //调用了resolve方法之后，promise对象的状态变成 成功


                    /*
                    let error = '数据读取失败';
                    reject(error);  //调用了reject方法之后,promise对象的状态变成 失败
                    */
                }, 1000)
            })

            // 2.调用promise对象的then方法
            // promise.then方法接收两个函数作为参数，第一个为成功的函数(有一个形参value)，第二个是失败的函数(有一个形参reason)
            promise.then(function(value) {
                // 成功函数：在promise中调用了resolve方法
                console.log(value);
            }, function(reason) {
                // 失败函数: 在promise中调用了reject方法
                console.log(reason);
            })




        // 读取文件案例(使用promise异步编程)
            const fs = require('fs');
            const promise = new Promise(function(resolve, reject) {
                fs.readFile('./read.txt', (err, data) => {
                    if(err) {
                        // 读取失败之后调用reject方法
                        reject(err);
                    }
                    // 读取成功调用resolve方法
                    resolve(data.toString());
                })
            })

            promise.then(function(value) {
                // 读取成功输出value
                console.log(value);
            }, function(reason) {
                // 读取失败输出reason
                console.log(reason)
            })



        // 使用promise异步编程(ajax网络请求)
            const promise = new Promise(function(resolve, reject) {
                const xhr = new XMLHttpRequest();
                xhr.open('GET', 'http://127.0.0.1:3000/get1');
                xhr.send();
                xhr.onload = function() {
                    if(xhr.status >= 200 && xhr.status < 300) {
                        // 成功调用的方法
                        resolve(xhr.response);
                    }else {
                        // 失败调用的方法
                        reject(xhr.status);
                    }
                }
            })
            promise.then(function(value) {
                // 成功调用的方法
                console.log(value);
            }, function(reason) {
                // 失败调用的方法
                console.log(reason);
            })







        // 调用then方法 then方法的返回结果是 promise对象，对象状态由回调函数的执行结果决定
            // 1.如果回调函数中返回的结果是 非promise 类型的属性，状态为成功，返回值为对象的成功值
            // 2.如果回调函数中返回的结果是 是promise对象并且调用了resolve函数，状态为成功，返回值为对象的成功值，如果调用了reject函数，状态为失败，返回值为失败值
            // 3.抛出错误，状态为失败，返回值就是抛出的错误
            let result = promise.then(value => {
                // 1.回调函数中返回的结果是非promise类型的属性
                // console.log(value);  //promiseState:fulfilled   promiseResult:ok
                // return value;

                // 2.返回promise对象
                // return new Promise((resolve, reject) => {
                //     resolve('ok');   //promiseState:fulfilled   promiseResult:ok
                //     reject('error');    //promiseState:rejected    promiseResult:error

                // 3.抛出错误
                // throw 'error';    //promiseState:rejected    promiseResult:error
                
            }, reason => {
                console.log(reason);
                return reason;
            })
            console.log(result);


            // then方法可以链式调用
            promise.then(value => {

            }, reason => {

            }).then(value => {

            }, reason => {
                
            })



        // 读取三个文件，按顺序读取
            // 原始写法,如果读取的文件在多一点，就会出现回调地狱的情况
            const fs = require('fs');
            fs.readFile('./txt/file01.txt', (error, data) => {
                fs.readFile('./txt/file02.txt', (error1, data1) => {
                    fs.readFile('./txt/file03.txt', (error2, data2) => {
                        let fileData = data + data1 + data2
                        console.log(fileData.toString())
                    })
                })
            })
            // 使用promise的方法来写(then方法可以链式调用)
            const fs = require('fs');
            new Promise((resolve, reject) => {
                fs.readFile('./txt/file01.txt', (err, data) => {
                    resolve(data);
                })
            }).then(value => {
                return new Promise((resolve, reject) => {
                    fs.readFile('./txt/file02.txt', (err, data) => {
                        resolve(value + data);
                    })
                })
            }).then(value => {
                return new Promise((resolve, reject) => {
                    fs.readFile('./txt/file03.txt', (err, data) => {
                        resolve(value + data);
                    })
                })
            }).then(value => {
                console.log(value);
            })

    // promise对象的catch方法
        // catch方法其实就是then方法传的第二个参数(失败的函数)
        // 完全可以用then方法的第二个参数代替











// Set
    // ES6提供了新的数据结构Set(集合)。它类似于数组，但成员的值都是唯一的
    // 集合实现了iterator接口，所有可以使用 [扩展运算符] 和 [for...of...]进行遍历
    // 集合的属性和方法
        // 1) size   返回集合的个数(属性)
        // 2) add    添加一个新元素，返回当前集合
        // 3) delete 删除当前元素，返回boolean值
        // 4) has    检测集合中是否包含某个元素，返回boolean
        // 5) clear  清空这个集合,没有返回值


    // set数据结构语法
        let set = new Set(['zhangsan', 'lisi', 'wangwu']);
        // 元素的个数
        console.log(set.size);
        // 添加一个元素
        console.log(set.add('xiaozhang'));
        // 删除zhangsan这个元素
        console.log(set.delete('zhangsan'));
        // 检测是否有lisi元素
        console.log(set.has('lisi'));
        // 清空set这个集合
        set.clear();
        console.log(set);

    // 案例练习
        const one = [1, 2, 3, 4, 5, 4, 3, 2, 1, 11];
        const two = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
        // 1.数组去重
        const quchong = [...new Set(one)];
        // 2.求交集(你有我也有的)
        const jiaoji = [...new Set(one)].filter(value => new Set(two).has(value) ? true : false);
        // 3.求并集(你的加上我的然后去掉重复的)
        const bingji = [...new Set([...one, ...two])];
        // 4.求差集(A和B的差集，就是A里面有的B里面没有的)
        const chaji = [...new Set(one)].filter(value => !(new Set(two).has(value)));
        console.log(chaji);









// Map
    // ES6提供了 Map 数据结构。它类似于对象，也是键值对的集合。
    // 但是“键”的范围不限于字符串。各种类型的值 (包括对象) 都可以当做键。
    // Map 也实现了 iterator 接口，所以可以使用 [扩展运算符] 和 [for...of...] 进行遍历。
    
    // Map 的属性和方法
        // 1) size    返回Map的元素个数
        // 2) set     增加一个新元素，返回当前Map
        // 3) get     返回键名对象的键值
        // 4) has     检测Map中是否包含某个元素，返回boolean值
        // 5) delete  删除某个元素,返回boolean值
        // 5) clear   清空集合, 返回undefined
    
    // 语法
        // 创建一个map
        const map = new Map();
        // 添加一个新元素
        const add = map.set('name', 'zhangsan');
        // 查看元素的个数
        const count = map.size;
        // 获取键名对应的值
        const keyValue = map.get('name');
        // 检查map中是否包含name元素
        const bool = map.has('name');
        // 删除某个元素
        const delBool = map.delete('name');
        // 清空集合
        map.clear();











// 类
    // es6的类和对象
    // 在JavaScript中，对象是一组无需的相关属性和方法的集合，所有的事物都是对象，例如字符串、数值、数组、函数等
    // 在es6中新增了类的概念，可以使用class关键字声明一个类，之后以这个类来实例化对象

    // 创建类
    // 语法
        class name {
            // class bodys
        }

    // constructor()方法是类的构造函数(默认方法)
    // 用于传递参数，返回实例对象
    // 通过new命令生成对象实例时，自动调用该方法。
    // 如果没有显示定义类，类内部会自动给我们创建一个constructor()
    // 例如：
        class info {
            constructor(name, age) {
                this.name = name;
                this.age = age;
            }
        }
        var ldh = new info('ldh', 18);
        var zhangsan = new info('zhangsan', 20);
        console.log(ldh.name);
        console.log(zhangsan.name);


    // 在类中添加共有方法
    // 添加公有方法pring,功能是打印自己的名字
    // 例如：
        class info {
            constructor(name, age) {
                this.name = name;
                this.age = age;
            }

            print() {
                console.log(this.name);
            }
        }
        var ldh = new info('ldh', 18);
        var zhangsan = new info('zhangsan', 20);
        ldh.print();
        zhangsan.print();







    // 类继承extends和super关键字
    // 继承：子类可以继承父类的一些属性和方法。
        // 例如：
            class Father { //父类
            }
            class Son extends Father { //子类继承父类
            }



    // super关键字用于访问和调用父类对象上的函数。可以调用父类的构造函数，也可以调用父类的普通函数。
        // 例如：
            class Father {
                constructor(x, y) {
                    this.x = x;
                    this.y = y;
                }
                count() {
                    console.log(this.x + this.y);
                }
            }
            class Son extends Father {
                constructor(x, y) {
                    super(x, y);    //调用父类中的构造函数(constructor)
                }
            }
            var zs = new Son(10, 20);
            var ls = new Son(20, 20);
            zs.count();
            ls.count();



    // 子类使用super关键字调用父类的普通方法
        class Father {
            constructor() {

            }
            print() {
                console.log('Father');
            }
        }

        class Son extends Father {
            constructor() {
                super();
            }
            print() {
                super.print();
            }
        }
        var zs = new Son();
        zs.print();


    // 继承的特点：
    // 1.继承中，如果实例化子类输出一个方法，先看子类有没有这个方法，如果有就先执行子类的
    // 2.继承中，如果子类里面没有，就去查找父类有没有这个方法，如果有，就执行父类的这个方法(就近原则)

    // super关键字必须放在this之前
    // 子类在构造函数中使用super，必须放到this前面(必须先调用父类的构造方法，在使用子类构造方法)
    // 例如：
        class Father {
            constructor(x, y) {
                this.x = x;
                this.y = y;
            }
            sum() {
                console.log(this.x + this.y);
            }
        }

        class Son extends Father {
            constructor(x, y, a, b) {
                super(x, y)
                this.a = a;
                this.b = b;
            }
            subtraction() {
                console.log(this.a - this.b);
            }
        }

        var zs = new Son(1, 1, 10, 5);
        zs.sum();
        zs.subtraction();


    // 三个注意点：
    // 1.在ES6中类没有变量提升，所以必须先定义类，才能通过类实例化对象。
    // 2.类里面的共有属性和方法一定要加this使用。
    // 3.类里面的this指向问题。
    // constructor 里面的this指向的是实例对象
    // 方法里面的this指向的是它的调用者


    // 使用实例化里面的参数，先在全局申请一个变量，然后在把constructor里面的this赋值给全局变量
        var obj;
        class click {
            constructor(x, y) {
                obj = this;
                this.x = x;
                this.y = y;
                this.btn = document.getElementsByClassName('btn')[0];
                this.btn.onclick = this.sum;
            }
            sum() {
                console.log(obj.x);
            }
        }
        var zs = new click(10, 20);
        
        

    // 静态成员
        // 静态成员只能是类使用，实例化对象无法使用
            class Phone{
                // 在前面加上static就是静态成员
                static name = 'xiaomi';
                static change() {
                    console.log('我是小米手机')
                }


                name = 'huawei';
                change() {
                    console.log('我是华为手机')
                }
            }
            let huawei = new Phone();
            huawei.change();
            Phone.change();
            console.log(Phone.name);
            console.log(huawei.name);


    // get 和 set  类和对象都有这两个方法
        // get : 只要读取了该属性就会执行该属性的方法
        // set : 只要修改(赋值)了该属性就会执行该方法
            class Phone{
                get price() {
                    console.log('价格属性被读取了')
                }
                set count(value) {  //set方法的形参必须有一个值
                    console.log(value);
                }
            }
            let phone = new Phone();
            phone.price;
            phone.count = 'test';












// 数值扩展
    // 1.Number.EPSILON 是 JavaScript 表示的最小精度
        console.log(Number.EPSILON); //返回2.220446049250313e-16

    // 2.二进制 八进制 十六进制
        let b = 0b1111;
        let o = 0o777;
        let x = 0xfff;
        console.log(b,o,x)

    // Number.isFinite 检测一个数是否为有限数
        console.log(Number.isFinite(10 / 999))

    // Number.isNaN 检测一个数值是否为NaN
        console.log(Number.isNaN(NaN))
    
    // Number.isInteger 判断一个数是否为整数
        console.log(Number.isInteger(10.5));

    // Math.trunc 将数字的小数部分抹掉
        console.log(Math.trunc(3.5));

    // Math.sign 判断一个数到底为正数、负数、还是零
    // 正数返回1   负数返回-1    零返回0
        console.log(Math.sign(100));
        console.log(Math.sign(0));
        console.log(Math.sign(-2000));











// ES6对象方法的扩展
    // 1.Object.is  判断两个值是否相等
        console.log(Object.is(11, 11));
    
    // 2.Object.assign 对象的合并(重复的属性后面的会替换前面的)
        let obj = {
            name : 'zhangsan',
        }
        let obj1 = {
            age : 18
        }
        let result = Object.assign(obj, obj1);
        
    // 3.Object.setPrototypeOf 设置原型对象
    //   Object.getPrototypeOf 获取原型对象

        const obj = {
            name : 'zhangsan'
        }
        const obj1 = {
            test : [55, 99, 33, 22]
        }
        Object.setPrototypeOf(obj, obj1);  //把obj的prototype指向obj1
        console.log(obj);
        Object.getPrototypeOf(obj);        //获取obj的原型，当前obj的原型是obj1
    












// 模块化
    // 模块化是将一个大的程序文件，拆分成许多小的文件，然后小的文件在组合起来。
    // 模块化的好处(模块化的优势有以下几点)：
        // 1) 方法命名冲突
        // 2) 代码复用
        // 3) 高维护性

    
    // 模块话规范产品
        // ES6之前的模块化规范有：
            // 1) CommonJs => NodeJs、Browserify
            // 2) AMD      => requireJs
            // 3) CMD      => seaJS


    // ES模块化语法
        // 模块化功能主要有两个命令构成: export 和 import
            // export 命令用于规定模块的对外接口
            // import 命令用于输入其他模块提供的功能





    // ES6 export暴露数据语法讲解
        // 1.统一暴露
            let school = '计算机学院';
            function teach() {
                console.log('教学');
            }
            export {school, teach};
        // 2.默认暴露
            export default {
                school : '计算机学院',
                teach() {
                    console.log('JavaScript教学');
                }
            }
        // 3.分别暴露(逐个暴露)
            export let school = '学校';
            export function teach() {
                console.log('testg');
            }


    // ES6 import 导入语法讲解
        // 1.通用的导入方法
            // import * as 变量名 from '路径'
            // 例如
            import * as module from './js.js';
        // 2.解构赋值形式
            // 导入分别暴露和同一暴露的语法
            import {school, teach} from './js.js';
            // 导入默认暴露的语法(需要给default设置一个别名，不能直接使用default)
            import {default as module} from './js2.js';
        // 简便形式(它只能针对默认暴露)
            import module from './js2.js';



    // export import 练习
        // 1.在js.js文件中写一个变量以及一个函数,并且使用export暴露变量和方法
        export let school = '惠州城市职业技术学院';
        export function teach(value) {
            console.log(`我可以学习${value}`);
        }
    // 2.在index.html中使用import导入js.js文件中暴露的所有数据
    /*
        <script type='module'>  //包含import 语句的都需要加上 type='module'
            //把js.js文件中所有export出来的内容都负值给js这个变量
            import * as js form './js.js';
        </script>
    */


    // js入口文件
        // 新建一个js文件
        // 把所有需要导入的代码都放到该文件中
        // 最后把该文件引入到index.html里面   <script src="./js/app.js" type="module"></script>










// babel(实验失败)
    // 官网：https://www.babeljs.cn/
    // babel是一个工具链，主要用于将采用ECMAscript 2015+语法编写的代码转换为
    // 向后兼容的JavaScript语法，一遍能够运行在当前和旧版本的浏览器或其他环境中。
    
    // 使用步骤
        // 1.安装工具
            // babel-cli ：babel-cli是babel的一个命令行工具
            // babel-preset-env ：是一个预设包，能够把ESMAscript2015+的语法转换成向后兼容的JavaScript语法。
            // browserify ：是一个打包工具，为啥没有选择webpakc作为打包工具，因为browseif比较简单，在项目中会使用webpack作为打包工具。

            // 例如：
                // npm -D 的意思就是报名会被注册在package.json的devDependencies里面，仅在开发环境中使用-D
                // npm install babel-cli babel-preset-env browserify -D   

        // 2.转换代码
            // npx babel [需要转换的文件路径] -d [放到哪里] --presets-babel-preset-env
            // 例如
                // $ npx babel ./js/ -d dir --presets=babel-preset-env
           
        // 3.打包
            // npx browserify [需要打包的文件路径] -o [打包完成的文件路径]
            







// 模块化引入npm包
    // 例如我要引入jquery
    // npm i jquery

    // import $ from 'jquery'; // const $ = require('jquery)
    // $('body').css('background', 'red');

    // 编译
    // npx babel js -d dist/js --presets=babel-preset-env
    
    // 打包
    // npx browserify dist/js/app.js -o dist/bundle.js








// ES7性新特性
// Array.prototype.includes
    // includes检测数组中是否包含某个元素,返回布尔类型的值
    // 例如：
        const arr = ['zhangsan', 'lisi', 'wangwu'];
        console.log(arr.includes('zhangsan')); //检查数组中是否包含zhangsan

        let str = 'zhangsanlisiwangwu';
        console.log(str.includes('zhansan'));  //检测这个字符串是否包含zhansan这几个字符
    

// 指数运算符
    // ** 幂运算(平方)
    // 例如
        console.log(2 ** 8);












// try catch finally(额外添加的知识点不属于ES6的知识点)
    // try{}catch(){}finally{} 语句一般是用来将有可能引发错误的代码（比如异步请求）放在try语句块中，并且对应一个响应，然后有异常被抛出到catch中。
        try{
            // 如果这里面没有报错就不会指向catch里面的代码
            console.loga('tr');
        }catch(error){
            // try里面报错了就执行catch里面的代码，catch方法有个参数error，返回错误对象
            console.log(error);
            console.log('catch');
        }finally{
            // 不管try有没有报错都会执行这里面的代码
            console.log('finally');
        }








// ECMAScript8 新特性


// async 和 await
    // async 和 await两种语法结合可以让异步代码像同步代码一样

    // async函数
        // 1) async 函数的返回值为promise对象。
        // 2) promise 对象的结果有async函数执行的返回值决定
        // 例如：
            async function fn() {
                // 返回的结果不是一个promise类型的对象，返回的结果就是成功的promise对象。
                // 抛出错误，返回的结果是一个失败的promise对象。
                // 返回的值如果是一个promise对象,promise对象里面调成功方法就是成功的，调失败方法就是失败的。
                return new Promise((resolve, reject) => {
                    reject('失败')
                })
            }
            const promise = fn();
            promise.then(value => {
                console.log(value);
            }).catch((reason => {
                console.log(reason);
            }))


    // await表达式
        // 1) await 必须写在 async 函数中
        // 2) await 右侧的表达式一般为promise对象
        // 3) await 返回的是promise成功的值
        // 4) await 的promise失败了，就会抛出异常，需要通过try...catch捕获处理
        // 案例：
            async function demo() {
                return new Promise((resolve, reject) => {
                    // resolve('成功');
                    reject('失败');
                })
            }
            async function main() {
                try{
                    let result = await demo();
                    console.log(result);
                }catch(error) {
                    console.log(error)
                }
            }
            main();
    


    

    // 案例1 (使用async 和 await 读取三个文件并且输出)
        const fs = require('fs');
        async function readData(dir) {
            return new Promise((resolve, reject) => {
                fs.readFile(dir, (error, data) => {
                    if(error) reject(error);
                    resolve(data.toString());
                })
            })
        }
        
        async function main() {
            let readfile01 = await readData('./txt/file01.txt');
            let readfile02 = await readData('./txt/file02.txt');
            let readfile03 = await readData('./txt/file03.txt');
            console.log(readfile01 + readfile02 + readfile03);
        }
        main();
    
    // 案例2(使用async 和 await 发送Ajax请求)
        async function sendAjax() {
            return new Promise((resolve, reject) => {
                const xhr = new XMLHttpRequest();
                xhr.open('GET', 'http://127.0.0.1:3000/get');
                xhr.send();
                xhr.onload = function() {
                    if(xhr.status >= 200 && xhr.status < 300) {
                        // 成功了
                        resolve(xhr.responseText);       
                    }else {
                        // 失败了
                        reject(xhr.status);
                    }
                }
            })
        }
        async function main() {
            try{
                let data = await sendAjax();
                console.log(data);
            }catch(error) {
                console.log(error);
            }
        }
        main();










// 对象方法(这个方法是ES5新添加的属性)
    Object.defineProperty(obj, prop, descriptor) //定义对象的新属性或原有属性
    // defineProperty 该方法需要传三个参数
    // obj : 必须。目标对象
    // prop ：必须。需定义或修改属性的名称
    // descriptor 
        // descriptor说明：以对象形式{ }书写
        // value : 设置属性的值，默认为undefined
        // writable ：值是否可以重写。true | false 默认false
        // enumerable : 目标属性是否可以被枚举。true | false 默认为false
        // configurable : 目标属性是否可以被删除或是否可以再次修改特性 true | false 默认为false


        // 例如:
        let obj = {
            name: 'zs',
            age: 18
        }

        Object.defineProperty(obj, 'language', {
            value: 'javaScript',
            writable : false,
            enumerable : true,
            configurable : false,
        })









// ES8对象方法扩展
    // 获取对象所有的键(返回数组)
        const key = Object.keys(obj);
    // 获取对象所有的值(返回数组)
        const value = Object.values(obj);
    // 获取对象的键值(返回数组,数组的第一位是键，第二位是值)
        const keyValue = Object.entries(obj);
    // 获取对象属性的描述对象(配套方法defineProperty)
        const descript = Object.getOwnPropertyDescriptors(obj);
    














// ES9
// 扩展运算符和rest参数
    // 对象合并
        const obj1 = {
            name : 'zhangsan',
            age : 18
        }
        const obj2 = {
            name : 'lisi',
            age : 20
        }
        const obj3 = {...obj1, ...obj2};

    
    // es9对于对象的rest参数的扩展
    function rest(ip, domain, ...user) {
        console.log(ip, domain, user);
    }
    rest({
        ip : '127.0.0.1',
        domain : 'localhost',
        userName : 'root',
        password : '123456789'
    })






// 正则扩展-命名捕获分组
    // 需要提取下面这个字符串里面的url以及value
    let str = '<a href="https://www.baidu.com">百度</a>';
    let reg = /<a href="(.*)">(.*)<\/a>/;
    let result = reg.exec(str);  //会返回一个数组,数组第一位是原始字符串，数组第二位是第一个(.*)的值，数组第三位是第二个(.*)的值
    // ["<a href=\"https://www.baidu.com\">百度</a>", "https://www.baidu.com", "百度", index: 0, input: "<a href=\"https://www.baidu.com\">百度</a>", groups: undefined]
    console.log(result);



    // 需要提取下面这个字符串里面的url以及value，并且设置别名
    let str = '<a href="https://www.baidu.com">百度</a>';
    let reg = /<a href="(?<url>.*)">(?<value>.*)<\/a>/;  //设置别名，别名放在伪数组groups方法里面
    let result = reg.exec(str); 
    console.log(result.groups);




// 正则扩展-正向断言
    // 我需要匹配555这三个数字
        let str = 'JS5211314你知道么555啦啦啦';
        const reg = /\d+(?=啦)/;
        let result = reg.exec(str);
        console.log(result);


// 正则扩展-反向断言
    // 我需要匹配555这组数据
        let str = 'JS5211314你知道么555啦啦啦';
        let reg = /(?<=么)\d+/;
        let result = reg.exec(str);
        console.log(result);




// 正则扩展-dotAll模式(没学明白懒得做笔记)
    // dot === .   而.在正则里面是一个元字符(除换行符以外的任意单个字符);
    
    









// ES10

// 用来创建对象(在方法里面传一个二维数组，数组的第一个值是key，第二个值是value)
    const obj = Object.fromEntries([
        ['name', 'zhansan'],
        ['age' , 18, 30],
    ])
// 这个方法可以将map数据类型转换成对象
    const map = new Map();
    map.set('zhangsan', 18).set('name', 'lisi');
    console.log(map);
    const obj = Object.fromEntries(map); //将map转换成数组
    console.log(obj);



// 清除字符串左右两侧的空格trim
    let str = '  my name is ZhangSan    ';
    console.log(str);
    // trimStart(清除字符串左侧空白)
        console.log(str.trimEnd());

    // trimEnd(清除字符串右侧空白)
        console.log(str.trimStart());


    // trim(清除字符串左右两侧的空白)
        console.log(str.trim());



// flat 平
    // 将多维数组转换成低位数组
        // 例如我要将一个二维数组转换成一个一维数组
        const arr = [1, 2, 3, [4, 5]];
        const result = arr.flat();


        // 例如我要将一个三维数组转换成一个一维数组
        const arr = [1, 2, 3, [4, 5, [6, 7]]];
        const result = arr.flat(2);  //需在里面传一个参数2

        // 例如我要将一个四维数组转换成一个一维数组
        const arr = [1, 2, 3, [4, 5, [6, 7, 8, [9, 10]]]];
        const result = arr.flat(3);  //在里面传一个参


// flatMap
    // flatMap这个方法起始就是flat和map的结合体
    const arr = [1, 2, 3];
    const result = arr.flatMap(value => value * 10)
    console.log(result);


// 获取Symbol的秒数   Symbol.description
    let sym = Symbol('my name is Symbol');
    let result = sym.description;
    console.log(result);  //输出my name is Symbol




















// ES11
// 私有属性(语法)
    // 在类里面声明私有属性：必须要在属性名前面加上#号代表私有属性
    // 私有属性无法在类的外部访问
    // 私有属性只能在类的内部访问
        class Person {
            // 公有属性
            name;

            // 私有属性(在前面加上#号)
            #age;
            #weight;

            constructor(name, age, weight) {
                this.name = name;

                // 私有属性
                this.#age = age;
                this.#weight = weight;
            }
            print() {
                //可以在类的内部访问私有属性
                console.log(this.#age);   
                console.log(this.#weight);
            }
        }

        const xiaohong = new Person('xiaohong', '18', '45kg');
        console.log(xiaohong);
        console.log(xiaohong.name);
        // console.log(xiaohong.#age);    //无法在类的外部访问私有属性
        // console.log(xiaohong.#weight);
        xiaohong.print();








// Promise.allSettled()
    // Promise.allSettled方法可以执行多个promise对象，获取多个promise对象的状态。
    // promise.allSettled方法相比于promise.all方法，可以获取成功或失败的所有状态。
    // 而Promise.all方法遇到一个reject方法就会立刻停止。


    // 案例：(Promise.allSettled)
        const p1 = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('成功的方法');
            }, 2000)
        })
    
        const p2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                reject('失败的方法')
            }, 3000)
        })

        const result = Promise.allSettled([p1, p2]);  //在方法里面传一个数组，数组里面存放需要执行的promise对象
        console.log(result);









// 可选链操作符
    // ?.
    // 问号点的意思就是判断前面的值有没有传入
    // 案例：
        function main(config) {
            // 我想要获取host:192.168.1.1这个值
            const dbHost = config?.db?.host;  //先判断config有没有，如果有的话再看db以此类推，如果config都没有的花就不会往后面看了
            console.log(dbHost);
        }
        main({
            db : {
                host : '192.168.1.1',
                username : 'root'
            },
            cache : {
                host : '192.168.1.2',
                username : 'administrator'
            }
        })







// 动态import()
    // 动态import就是当我要一股脑的吧所有东西都先import进来的话，这样子如果有些
    // 模块用不上，并且它又加载出来了这样就会造成浪费性能的情况
    // 动态import正式解决上面问题的解决方案，实现按需加载
    // 动态import()方法返回的是一个promise对象
    // 动态import()方法语法：
        const promise = import('文件路径'); //返回promise对象

        // 例如：
            // 我要实现一个点击弹出alert的功能，但是我想把alert这个功能的方法单独放到另外一个文件里
            // 当我需要使用的使用在导入进来

            const btn = document.querySelector('button');
            btn.addEventListener('click', function() {
                const promise = import('./file01.js');  //当我点击按钮的时候在加载进来
                console.log(promise);
                promise.then(module => {
                    module.helloWork();
                })
            })









// BigInt(大整形)
    // 语法
        let n = 521n;
        console.log(n, typeof(n))

    // 获取number的最大数值(es5知识点)
    Number.MAX_SAFE_INTEGER;








// golbalThis(全局this，无论你在任何环境中它始终是指向全局的)
    // window === globalThis
    



// hasOwnProperty(判断一个对象中有没有这个某个方法)
const obj = {
    name: '张三',
    age: 18,
    sex: 20
}
// 如果obj中有name这个属性返回true，没有的话返回false
const result = obj.hasOwnProperty('name')